﻿#region Apache 2.0 License header

//   Copyright 2012 Lesley van Zijl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. 

#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;

namespace FavourObjects.Extensions
{
    /// <summary>
    /// Extension methods for extending the Type class.
    /// Addded methods for making comparing members easier.
    /// </summary>
    public static class TypeExtensions
    {
        /// <summary>
        /// Checks if the specified property is available on the specified type.
        /// </summary>
        /// <param name="type">The type to explore.</param>
        /// <param name="property">The property to look for.</param>
        /// <returns>True if the property is available on the specified type.</returns>
        public static bool HasProperty(this Type type, PropertyInfo property)
        {
            Contract.Requires(type != null);
            Contract.Requires(property != null);

            PropertyInfo matchingMember = type.GetProperty(property.Name,
                                                           BindingFlags.NonPublic | BindingFlags.Public |
                                                           BindingFlags.Static | BindingFlags.Instance,
                                                           null, property.PropertyType, Type.EmptyTypes, null);

            return matchingMember != null;
        }

        /// <summary>
        /// Checks if a field with the specified name is available on the specified type.
        /// </summary>
        /// <param name="type">The type to explore.</param>
        /// <param name="name">The name of the field to look for.</param>
        /// <returns>True if the field is available on the specified type.</returns>
        public static bool HasField(this Type type, String name)
        {
            Contract.Requires(type != null);
            Contract.Requires(!String.IsNullOrEmpty(name));

            FieldInfo matchingMember = type.GetField(name,
                                                     BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static |
                                                     BindingFlags.Instance);

            return matchingMember != null;
        }

        /// <summary>
        /// Checks if the specified method is available on the specified type.
        /// </summary>
        /// <param name="type">The type to explore.</param>
        /// <param name="method">The method to look for.</param>
        /// <returns>True if the method is available on the specified type.</returns>
        public static bool HasMethod(this Type type, MethodInfo method)
        {
            Contract.Requires(type != null);
            Contract.Requires(method != null);

            return GetMatchingMethod(type, method) != null;
        }

        /// <summary>
        /// Retrieves a MethodInfo from the specified type that has the same name, amount of generic arguments
        /// and same method arguments.
        /// </summary>
        /// <param name="type">The type from which the method is retrieved.</param>
        /// <param name="name">The name of the method.</param>
        /// <param name="genericArgumentCount">Amount of generic argument types.</param>
        /// <param name="argumentTypes">Collection of arguments</param>
        /// <returns>A method info that satisfies all the specified conditions.</returns>
        public static MethodInfo GetMethod(this Type type, String name, int genericArgumentCount,
                                           params Type[] argumentTypes)
        {
            Contract.Requires(type != null);
            Contract.Requires(!String.IsNullOrEmpty(name));

            IList<MethodInfo> matchingMembers = type.GetMethods(BindingFlags.NonPublic |
                                                                BindingFlags.Public |
                                                                BindingFlags.Static |
                                                                BindingFlags.Instance).Where(m => m.Name == name).ToList();

            if (matchingMembers.Count > 1)
            {
                matchingMembers = matchingMembers.Where(m =>
                                                        m.GetGenericArguments().Count() == genericArgumentCount &&
                                                        argumentTypes.SequenceEqual(
                                                            m.GetParameters().Select(arg => arg.ParameterType),
                                                            new DeepTypeComparer())).ToList();
            }

            MethodInfo matchingMember;
            switch (matchingMembers.Count)
            {
                case 0:
                    matchingMember = null;
                    break;
                case 1:
                    matchingMember = matchingMembers.First();
                    break;
                default:
                    throw new AmbiguousMatchException("More then one matching method was found when looking for " + matchingMembers.First());
            }

            return matchingMember;
        }

        /// <summary>
        /// Retrieves a method from the specified type that has the same properties as the specified method.
        /// </summary>
        /// <param name="type">The type from which the method is retrieved.</param>
        /// <param name="method">The method to look for.</param>
        /// <returns>The method if it has been found, null otherwise.</returns>
        public static MethodInfo GetMatchingMethod(this Type type, MethodInfo method)
        {
            Contract.Requires(type != null);
            Contract.Requires(method != null);

            string name = method.Name;
            int genericArgumentsCount = method.GetGenericArguments().Count();
            ParameterInfo[] arguments = method.GetParameters();

            return type.GetMethod(name, genericArgumentsCount, arguments.Select(arg => arg.ParameterType).ToArray());
        }
    }
}